#include "XmlFileWriter.h"
#include "CommonUtils.h"

XmlFileWriter::XmlFileWriter()
	: m_pFile(NULL)
{
}

XmlFileWriter::~XmlFileWriter()
{
	// Make sure we closed every element.
	ASSERT(m_Elements.empty());

	if (m_pFile)
	{
		fclose(m_pFile);
		m_pFile = NULL;
	}
}

bool XmlFileWriter::SetPath(const string& path)
{
	// Make sure we don't have a file yet.
	ASSERT(m_pFile == NULL);

	// Open.
	m_pFile = fopen(path.c_str(), "wt");

	// Check.
	if (m_pFile == NULL) ERROR_AND_RET("could not open file " << path, false);

	// Okay.
	return true;
}

void XmlFileWriter::WriteIndentation(size_t skip)
{
	size_t Count = m_Elements.size();
	for (size_t i = skip; i < Count; i++)
		fputc('\t', m_pFile);
}

void XmlFileWriter::OpenElement(const string& name)
{
	ASSERT(m_pFile != NULL);

	// Close current begin tag if necessary.
	if (!m_Elements.empty() && !m_Elements.top().m_HasElements)
	{
		m_Elements.top().m_HasElements = true;
		fputs(">\n", m_pFile);
	}

	// Write indentation.
	WriteIndentation();

	// Write element begin tag.
	fprintf(m_pFile, "<%s", name.c_str());

	// Push element on stack.
	m_Elements.push(XmlElement(name));
}

void XmlFileWriter::CloseElement()
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty());

	// Pop top element.
	XmlElement CurrentElement(m_Elements.top());
	m_Elements.pop();

	// If the element contains elements...
	if (CurrentElement.m_HasElements)
	{
		// Write indentation.
		WriteIndentation();

		// Write full end element.
		fprintf(m_pFile, "</%s>\n", CurrentElement.m_Name.c_str());
	}
	// If the element has content...
	else if (CurrentElement.m_HasContent)
	{
		// Write full end element.
		fprintf(m_pFile, "</%s>\n", CurrentElement.m_Name.c_str());
	}
	else
	{
		// Write short end element.
		fputs(" />\n", m_pFile);
	}
}

void XmlFileWriter::AddAttribute(const string& name, int value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%d\"", name.c_str(), value);
}

void XmlFileWriter::AddAttribute(const string& name, bool value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	if (value)
		fprintf(m_pFile, " %s=\"true\"", name.c_str());
	else
		fprintf(m_pFile, " %s=\"false\"", name.c_str());
}

void XmlFileWriter::AddAttribute(const string& name, float value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%g\"", name.c_str(), value);
}

void XmlFileWriter::AddAttribute(const string& name, double value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%g\"", name.c_str(), value);
}

void XmlFileWriter::AddAttribute(const string& name, unsigned int value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%d\"", name.c_str(), value);
}

void XmlFileWriter::AddAttribute(const string& name, const string& value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%s\"", name.c_str(), value.c_str());
}

void XmlFileWriter::AddAttribute(const string& name, time_t value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasContent && !m_Elements.top().m_HasElements);

	// Convert.
	char Buffer[64];
	ConvertDateTimeToISO8601Ansi(value, Buffer, 64);

	// Write attribute.
	fprintf(m_pFile, " %s=\"%s\"", name.c_str(), Buffer);
}

void XmlFileWriter::AddContent(int value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	fprintf(m_pFile, "%d", value);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const int* buffer, int count)
{
	// Make sure we have an element open.
	ASSERT(buffer != NULL && count >= 0 && m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (count > 0)
		fprintf(m_pFile, "%d", buffer[0]);
	for (int i = 1; i < count; i++)
		fprintf(m_pFile, " %d", buffer[i]);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(bool value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (value)
		fputs("true", m_pFile);
	else
		fputs("false", m_pFile);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const bool* buffer, int count)
{
	// Make sure we have an element open.
	ASSERT(buffer != NULL && count >= 0 && m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (count > 0)
		fputs(buffer[0] ? "true" : "false", m_pFile);
	for (int i = 1; i < count; i++)
		fputs(buffer[i] ? " true" : " false", m_pFile);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(float value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	fprintf(m_pFile, "%g", value);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const float* buffer, int count)
{
	// Make sure we have an element open.
	ASSERT(buffer != NULL && count >= 0 && m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (count > 0)
		fprintf(m_pFile, "%g", buffer[0]);
	for (int i = 1; i < count; i++)
		fprintf(m_pFile, " %g", buffer[i]);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(double value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	fprintf(m_pFile, "%g", value);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const double* buffer, int count)
{
	// Make sure we have an element open.
	ASSERT(buffer != NULL && count >= 0 && m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (count > 0)
		fprintf(m_pFile, "%g", buffer[0]);
	for (int i = 1; i < count; i++)
		fprintf(m_pFile, " %g", buffer[i]);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(unsigned int value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	fprintf(m_pFile, "%d", value);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const string& value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	WriteEscaped(value.c_str(), value.length());
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(time_t value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Convert.
	char Buffer[64];
	ConvertDateTimeToISO8601Ansi(value, Buffer, 64);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	fputs(Buffer, m_pFile);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContent(const unsigned char* buffer, int count)
{
	// Make sure we have an element open.
	ASSERT(buffer != NULL && count >= 0 && m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements);

	// Close begin element if necessary.
	if (!m_Elements.top().m_HasContent) fputc('>', m_pFile);

	// Write content.
	if (m_Elements.top().m_HasContent) fputc(' ', m_pFile);
	if (count > 0)
		fprintf(m_pFile, "%2X", buffer[0]);
	for (int i = 1; i < count; i++)
		fprintf(m_pFile, " %2X", buffer[i]);
	m_Elements.top().m_HasContent = true;
}

void XmlFileWriter::AddContentCData(const string& value)
{
	// Make sure we have an element open.
	ASSERT(m_pFile != NULL && !m_Elements.empty() && !m_Elements.top().m_HasElements && !m_Elements.top().m_HasContent);

	// Close begin element if necessary (always the case since it cannot have content yet).
	fputc('>', m_pFile);

	// Write content.
	fputs("\n", m_pFile);
	WriteIndentation(1);
	fputs("<![CDATA[", m_pFile);
	fputs(value.c_str(), m_pFile);
	fputs("]]>\n", m_pFile);
	m_Elements.top().m_HasElements = true; // we consider it an element syntaxically.
}

void XmlFileWriter::WriteEscaped(const char* pString, unsigned int dwLength)
{
	unsigned int pos = 0;
	for (unsigned int i = 0; i < dwLength; i++)
		switch (pString[i])
		{
			case '&':
				fwrite(pString + pos, sizeof(char), i - pos, m_pFile);
				fputs("&amp;", m_pFile);
				pos = i + 1;
				break;
			case '<':
				fwrite(pString + pos, sizeof(char), i - pos, m_pFile);
				fputs("&lt;", m_pFile);
				pos = i + 1;
				break;
			case '>':
				fwrite(pString + pos, sizeof(char), i - pos, m_pFile);
				fputs("&gt;", m_pFile);
				pos = i + 1;
				break;
		}
	fwrite(pString + pos, sizeof(char), dwLength - pos, m_pFile);
}
